/*
 *  Copyright 2016 The WebRTC Project Authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#ifndef RTC_BASE_TYPE_TRAITS_H_
#define RTC_BASE_TYPE_TRAITS_H_

#include <cstddef>
#include <type_traits>

namespace rtc {

// Determines if the given class has zero-argument .data() and .size() methods
// whose return values are convertible to T* and size_t, respectively.
    template<typename DS, typename T>
    class HasDataAndSize {
    private:
        template<
                typename C,
                typename std::enable_if<
                        std::is_convertible<decltype(std::declval<C>().data()), T *>::value &&
                        std::is_convertible<decltype(std::declval<C>().size()),
                                std::size_t>::value>::type * = nullptr>
        static int Test(int);

        template<typename>
        static char Test(...);

    public:
        static constexpr bool value = std::is_same<decltype(Test<DS>(0)), int>::value;
    };

    namespace test_has_data_and_size {

        template<typename DR, typename SR>
        struct Test1 {
            DR data();

            SR size();
        };

        static_assert(HasDataAndSize<Test1<int *, int>, int>::value, "");
        static_assert(HasDataAndSize<Test1<int *, int>, const int>::value, "");
        static_assert(HasDataAndSize<Test1<const int *, int>, const int>::value, "");
        static_assert(!HasDataAndSize<Test1<const int *, int>, int>::value,
                      "implicit cast of const int* to int*");
        static_assert(!HasDataAndSize<Test1<char *, size_t>, int>::value,
                      "implicit cast of char* to int*");

        struct Test2 {
            int *data;
            size_t size;
        };
        static_assert(!HasDataAndSize<Test2, int>::value,
                      ".data and .size aren't functions");

        struct Test3 {
            int *data();
        };

        static_assert(!HasDataAndSize<Test3, int>::value, ".size() is missing");

        class Test4 {
            int *data();

            size_t size();
        };

        static_assert(!HasDataAndSize<Test4, int>::value,
                      ".data() and .size() are private");

    }  // namespace test_has_data_and_size

    namespace type_traits_impl {

// Determines if the given type is an enum that converts implicitly to
// an integral type.
        template<typename T>
        struct IsIntEnum {
        private:
            // This overload is used if the type is an enum, and unary plus
            // compiles and turns it into an integral type.
            template<typename X,
                    typename std::enable_if<
                            std::is_enum<X>::value &&
                            std::is_integral<decltype(+std::declval<X>())>::value>::type * =
                    nullptr>
            static int Test(int);

            // Otherwise, this overload is used.
            template<typename>
            static char Test(...);

        public:
            static constexpr bool value =
                    std::is_same<decltype(Test<typename std::remove_reference<T>::type>(0)),
                            int>::value;
        };

    }  // namespace type_traits_impl

// Determines if the given type is integral, or an enum that
// converts implicitly to an integral type.
    template<typename T>
    struct IsIntlike {
    private:
        using X = typename std::remove_reference<T>::type;

    public:
        static constexpr bool value =
                std::is_integral<X>::value || type_traits_impl::IsIntEnum<X>::value;
    };

    namespace test_enum_intlike {

        enum E1 {
            e1
        };
        enum {
            e2
        };
        enum class E3 {
            e3
        };
        struct S {
        };

        static_assert(type_traits_impl::IsIntEnum<E1>::value, "");
        static_assert(type_traits_impl::IsIntEnum<decltype(e2)>::value, "");
        static_assert(!type_traits_impl::IsIntEnum<E3>::value, "");
        static_assert(!type_traits_impl::IsIntEnum<int>::value, "");
        static_assert(!type_traits_impl::IsIntEnum<float>::value, "");
        static_assert(!type_traits_impl::IsIntEnum<S>::value, "");

        static_assert(IsIntlike<E1>::value, "");
        static_assert(IsIntlike<decltype(e2)>::value, "");
        static_assert(!IsIntlike<E3>::value, "");
        static_assert(IsIntlike<int>::value, "");
        static_assert(!IsIntlike<float>::value, "");
        static_assert(!IsIntlike<S>::value, "");

    }  // namespace test_enum_intlike

}  // namespace rtc

#endif  // RTC_BASE_TYPE_TRAITS_H_
